﻿using Exiled.API.Enums;
using Exiled.API.Features;
using Exiled.API.Features.Pools;
using Newtonsoft.Json;
using RgCore.PlayerHud;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Utils.NonAllocLINQ;

namespace RgCore.Modules
{
    public interface IConfig
    {
    }

    public class PluginInfo
    {
        public PluginInfo(RgPlugin instance, Assembly assembly, string fileLocation)
        {
            Instance = instance;
            Assembly = assembly;
            FileLocation = fileLocation;
        }

        public RgPlugin Instance { get; }
        public Assembly Assembly { get; }
        public string FileLocation { get; }
    }

    public static class ModuleManager
    {
        public static readonly string SubPluginPath = Path.Combine(Paths.Plugins, "RgSubPlugins");
        public static readonly string ConfigPath = Path.Combine(Paths.Plugins, "RgConfigs", Server.Port.ToString());
        public static List<PluginInfo> Plugins { get; set; } = new List<PluginInfo>();
        public static List<PluginInfo> DisabledPlugins { get; } = new List<PluginInfo>();

        public static void AnnouncePluginLoad(string category, string name)
        {
            foreach (Player player in Player.List)
                player.SendFormattedMsg("hotfix", "<color=#ff736a>热更新</color>",
                    $"[<color=#ff00a4>{category}</color>] <color=#5893b9>{name}</color> [<color=#19FF00>加载</color>]",
                    4, "<color=#FF2222>🔧</color>", ScreenLocation.CenterTop, true);
        }

        public static void AnnouncePluginUnload(string category, string name)
        {
            foreach (Player player in Player.List)
                player.SendFormattedMsg("hotfix", "<color=#ff736a>热更新</color>",
                    $"[<color=#ff00a4>{category}</color>] <color=#5893b9>{name}</color> [<color=#cc4343>卸载</color>]",
                    4, "<color=#FF2222>🔧</color>", ScreenLocation.CenterTop, true);
        }

        public static void AnnouncePluginReload(string category, string name)
        {
            foreach (Player player in Player.List)
                player.SendFormattedMsg("hotfix", "<color=#ff736a>热更新</color>",
                    $"[<color=#ff00a4>{category}</color>] <color=#5893b9>{name}</color> [<color=#ff9300>重载</color>]",
                    4, "<color=#FF2222>🔧</color>", ScreenLocation.CenterTop, true);
        }

        public static void AnnouncePluginDisable(string category, string name)
        {
            foreach (Player player in Player.List)
                player.SendFormattedMsg("hotfix", "<color=#ff736a>热更新</color>",
                    $"[<color=#ff00a4>{category}</color>] <color=#5893b9>{name}</color> [<color=#ff9300>禁用</color>]",
                    4, "<color=#FF2222>🔧</color>", ScreenLocation.CenterTop, true);
        }

        public static void AnnouncePluginEnable(string category, string name)
        {
            foreach (Player player in Player.List)
                player.SendFormattedMsg("hotfix", "<color=#ff736a>热更新</color>",
                    $"[<color=#ff00a4>{category}</color>] <color=#5893b9>{name}</color> [<color=#19FF00>启用</color>]",
                    4, "<color=#FF2222>🔧</color>", ScreenLocation.CenterTop, true);
        }

        public static bool IsLoaded(string id) => Enumerable.Any(Plugins, x => x.Instance.Id == id);

        public static bool IsDisable(string id) => Enumerable.Any(DisabledPlugins, plugin => plugin.Instance.Id == id);

        public static void EnablePlugin(string id, bool isAnnounce)
        {
            if (!DisabledPlugins.TryGetFirst(x => x.Instance.Id == id, out PluginInfo plugin))
            {
                return;
            }

            string name = plugin.Instance.Name;
            plugin.Instance.OnEnabled();
            DisabledPlugins.Remove(plugin);
            Plugins.Add(plugin);
            if (isAnnounce)
            {
                AnnouncePluginEnable("集成插件", name);
            }

            Log.Info($"集成插件 {name} 已启用");
        }

        public static void DisablePlugin(string id, bool isAnnounce)
        {
            if (!Plugins.TryGetFirst(x => x.Instance.Id == id, out PluginInfo plugin))
            {
                return;
            }

            if (!plugin.Instance.CanUnload)
            {
                Log.Warn("尝试禁用一个不可卸载的插件，已阻止");
                return;
            }

            string name = plugin.Instance.Name;
            plugin.Instance.OnDisabled();
            Plugins.Remove(plugin);
            DisabledPlugins.Add(plugin);
            if (isAnnounce)
            {
                AnnouncePluginDisable("集成插件", name);
            }

            Log.Info($"集成插件 {name} 已禁用");
        }

        public static void ReloadPlugin(string id)
        {
            if (Plugins.Any( p => p.Instance.Id == id))
            {
                var key = Plugins.FirstOrDefault(p => p.Instance.Id == id);
                if (key.Instance.CanUnload)
                {
                    string name = key.Instance.Name;
                    string location = key.FileLocation;
                    Plugins.Remove(key);
                    key.Instance.OnDisabled();
                    LoadPlugin(location, false);
                    AnnouncePluginReload("集成插件", name);
                    Log.Info($"集成插件 {name} 已重新加载");
                }
                else
                {
                    Log.Warn("尝试卸载一个不可卸载的插件，已阻止");
                }
            }
        }

        public static void UnloadPlugin(string id)
        {
            if (Enumerable.Any(Plugins, p => p.Instance.Id == id))
            {
                var key = Plugins.FirstOrDefault(p => p.Instance.Id == id);
                if (key.Instance.CanUnload)
                {
                    string name = key.Instance.Name;
                    string location = key.FileLocation;
                    Plugins.Remove(key);
                    key.Instance.OnDisabled();
                    AnnouncePluginUnload("集成插件", name);
                    Log.Info($"集成插件 {name} 已卸载");
                }
                else
                {
                    Log.Warn("尝试卸载一个不可卸载的插件，已阻止");
                }
            }
        }

        public static RgPlugin LoadPlugin(string path, bool announce)
        {
            string filename = path.Split('\\').Last().Replace(".dll", "");
            Assembly assembly = Assembly.Load(File.ReadAllBytes(path));
            if (assembly != null)
            {
                var type = assembly.GetTypes().FirstOrDefault(x => x.BaseType == typeof(RgPlugin));
                if (type == null)
                {
                    Log.Warn("文件" + filename + "没有任何插件入口，已忽略");
                    return null;
                }

                RgPlugin rp = (RgPlugin)Activator.CreateInstance(type);
                rp.LoadConfig();
                var info = new PluginInfo(rp, assembly, path);
                Plugins.Add(info);
                rp.OnEnabled();
                Log.Info($"集成插件 {rp.Name} 已加载");
                if (announce)
                {
                    AnnouncePluginLoad("集成插件", rp.Name);
                }
            }

            return null;
        }

        public static void LoadPlugins()
        {
            var files = Directory.GetFiles(SubPluginPath, "*.dll");
            PluginPriority[] p = new PluginPriority[]
            {
                PluginPriority.Highest, PluginPriority.Higher, PluginPriority.High,
                PluginPriority.Medium, PluginPriority.Low, PluginPriority.Lower, PluginPriority.Lowest
            };
            foreach (var path in files)
            {
                string filename = path.Split('\\').Last().Replace(".dll", "");
                if (RgCore.instance.Config.WhitelistMode != RgCore.instance.Config.PluginNameList.Contains(filename))
                {
                    Log.SendRaw("忽略加载：" + filename, ConsoleColor.Yellow);
                    continue;
                }

                Assembly assembly = Assembly.Load(File.ReadAllBytes(path));
                if (assembly != null)
                {
                    var type = assembly.GetTypes().FirstOrDefault(x => x.BaseType == typeof(RgPlugin));
                    if (type == null)
                    {
                        Log.Warn("文件" + filename + "没有任何插件入口，已忽略");
                        continue;
                    }

                    RgPlugin rp = (RgPlugin)Activator.CreateInstance(type);
                    rp.LoadConfig();
                    Plugins.Add(new PluginInfo(rp, assembly, path));
                }
            }

            for (int i = 0; i < p.Length; i++)
            {
                foreach (var rp in Plugins.Where(x => x.Instance.Priority == p[i]))
                {
                    rp.Instance.OnEnabled();
                    Log.Info($"集成插件 {rp.Instance.Name} 已加载");
                }
            }
        }
    }

    public abstract class RgPlugin : IDisposable
    {
        public RgPlugin()
        {
        }

        public virtual bool CanUnload { get; } = true;
        public virtual object Config { get; set; } = null;
        public abstract string Name { get; }
        public abstract string Id { get; }
        public virtual PluginPriority Priority { get; } = PluginPriority.Default;

        public void LoadConfig()
        {
            if (Config == null)
                return;
            try
            {
                Directory.CreateDirectory(ModuleManager.ConfigPath);
                var path = Path.Combine(ModuleManager.ConfigPath, $"{Name}-{Id}.json");
                if (!File.Exists(path))
                {
                    Log.Info($"生成配置文件 {Id}（{Name}）");
                    var str = JsonConvert.SerializeObject(Config, Formatting.Indented);
                    File.WriteAllText(path, str);
                    return;
                }

                Config = JsonConvert.DeserializeObject(File.ReadAllText(path), Config.GetType());
            }
            catch (Exception e)
            {
                Log.Error(e);
            }
        }

        public virtual void OnEnabled()
        {
        }

        public virtual void OnDisabled()
        {
        }

        public void Dispose()
        {
        }
    }

    public interface IName
    {
        string Name { get; }
    }

    public class SubPluginManager<T> where T : Attribute, IName
    {
        public SubPluginManager(string name, string foldername, Type basetype)
        {
            Name = name;
            FolderPath = Path.Combine(ModuleManager.SubPluginPath, foldername);
            Directory.CreateDirectory(FolderPath);
            BaseType = basetype;
        }

        public Type BaseType { get; }
        public string FolderPath { get; }
        public string Name { get; }

        public event Action<Type> ObjectUnloaded;

        public event Action<Type> ObjectLoaded;

        //类型, 目录
        public Dictionary<Type, string> AllTypes { get; set; } = new Dictionary<Type, string>();

        public void ReloadObject(string classname)
        {
            var filter = AllTypes.FirstOrDefault(p => p.Key.Name == classname);
            if (filter.Key == null)
                return;
            string location = filter.Value;
            var list = AllTypes.Where(p => p.Value == location).ToList();
            foreach (var item in list)
            {
                AllTypes.Remove(item.Key);
                LoadObject(location, false, out Type[] ts);
                foreach (var t in ts)
                {
                    var attr = t.GetCustomAttribute<T>();
                    ModuleManager.AnnouncePluginReload(Name, attr.Name);
                }
            }
        }

        public void DisableObject(string classname, bool announce)
        {
            if (Enumerable.Any(AllTypes, p => p.Key.Name == classname))
            {
                var key = AllTypes.FirstOrDefault(p => p.Key.Name == classname);
                AllTypes.Remove(key.Key);
                var attr = key.Key.GetCustomAttribute<T>();
                if (announce)
                {
                    ModuleManager.AnnouncePluginUnload(Name, attr.Name);
                }

                ObjectUnloaded?.Invoke(key.Key);
                Log.Send($"[{Name}] 【{attr.Name}】 已卸载", Discord.LogLevel.Info, ConsoleColor.Yellow);
            }
        }

        public bool LoadObject(string path, bool announce, out Type[] type)
        {
            bool result = false;
            type = null;
            Assembly assembly = Assembly.Load(File.ReadAllBytes(path));
            List<Type> types = ListPool<Type>.Pool.Get();
            if (assembly != null)
            {
                foreach (Type t in assembly.GetTypes())
                {
                    if (t.BaseType == BaseType && t.GetCustomAttribute<T>() != null)
                    {
                        if (Enumerable.Any(AllTypes, x => x.Key.Name == t.Name))
                            continue;
                        var attr = t.GetCustomAttribute<T>();
                        AllTypes.Add(t, path);
                        types.Add(t);
                        Log.Send($"[{Name}] 【{attr.Name}】 已加载", Discord.LogLevel.Info, ConsoleColor.Green);
                        if (announce)
                        {
                            ModuleManager.AnnouncePluginLoad(Name, attr.Name);
                        }

                        result = true;
                        ObjectLoaded?.Invoke(t);
                    }
                }
            }

            type = ListPool<Type>.Pool.ToArrayReturn(types);
            return result;
        }

        public void LoadObjects()
        {
            foreach (string file in Directory.GetFiles(FolderPath, "*.dll"))
            {
                LoadObject(file, false, out _);
            }
        }
    }
}